# forward-compat boilerplate
from __future__ import absolute_import
from __future__ import with_statement
__metaclass__ = type

import time
import Queue
import smtplib
import threading

from email import encoders
from email.header import make_header
from email.utils import make_msgid
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


__metaclass__ = type


class Message:
    def __init__(self, To=None, From=None, Cc=None, Bcc=None,
                 Date=None, Subject=None, TextBody=None, HtmlBody=None,
                 attachments=None, charset='US-ASCII'):
        self.attachments = []
        for a in attachments or []:
            self.add_attachment(a)
        self.To = To
        self.Cc = Cc
        self.Bcc = Bcc
        self.From = From
        self.Subject = Subject
        self.TextBody = TextBody
        self.HtmlBody = HtmlBody
        self.Date = Date or time.strftime('%a, %d %b %Y %H:%M:%S %z', time.gmtime())
        self.charset = charset
        self.message_id = make_msgid()

    def add_attachment(self, attachment):
        if isinstance(attachment, basestring):
            self.attachments.append((attachment, None))
        else:
            try:
                filename, cid = attachment
            except (TypeError, IndexError):
                self.attachments.append((attachment, None))
            else:
                self.attachments.append((filename, cid))

    def __str__(self):
        if self.attachments:
            msg = self._multipart()
        else:
            msg = self._text()
        self._write_headers(msg)
        return msg.as_string()

    def _text(self):
        if self.HtmlBody:
            return self._with_html()
        return MIMEText(self.TextBody, 'plain', self.charset)

    def _with_html(self):
        outer = MIMEMultipart('alternative')
        part1 = MIMEText(self.TextBody, 'plain', self.charset)
        part2 = MIMEText(self.HtmlBody, 'html', self.charset)
        outer.attach(part1)
        outer.attach(part2)
        return outer

    def _write_headers(self, msg):
        msg['Date'] = self.Date
        msg['From'] = self.From
        msg['Message-Id'] = self.message_id
        subject = self.Subject
        if self.charset != 'us-ascii':
            subject = unicode(self.Subject, self.charset)
            subject = str(make_header([(subject, self.charset)]))
        msg['Subject'] = subject

        to = self.To
        if not isinstance(to, basestring):
            to = ', '.join(list(to))
        msg['To'] = to

        cc = self.Cc
        if cc and not isinstance(cc, basestring):
            cc = ', '.join(list(cc))
        if cc:
            msg['Cc'] = cc

    def _multipart(self):
        msg = MIMEMultipart('related')
        msg.preamble = self.Subject

        if self.HtmlBody:
            outer = MIMEMultipart('alternative')

            part1 = MIMEText(self.TextBody, 'plain', self.charset)
            part1.add_header('Content-Disposition', 'inline')

            part2 = MIMEText(self.HtmlBody, 'html', self.charset)
            part2.add_header('Content-Disposition', 'inline')

            outer.attach(part1)
            outer.attach(part2)
            msg.attach(outer)
        else:
            msg.attach(MIMEText(self.TextBody, 'plain', self.charset))

        for filename, cid in self.attachments:
            msg.attach(self.put_attachment(filename, cid))

        return msg

    def put_attachment(self, body, ctype, filename=None, cid=None):
        maintype, subtype = ctype.split('/', 1)
        M = {'text': MIMEText,
             'image': MIMEImage,
             'audio': MIMEAudio}.get(maintype)
        if M:
            msg = M(body, _subtype=subtype)
        else:
            msg = MIMEBase(maintype, subtype)
            msg.set_payload(body)
            encoders.encode_base64(msg)

        if cid:
            msg.add_header('Content-ID', '<%s>' % cid)
            msg.add_header('Content-Disposition', 'inline')
        else:
            msg.add_header('Content-Disposition', 'attachment', filename=filename)
        return msg

    def attach(self, filename, cid=None):
        self.attachments.append((filename, cid))

    def __iter__(self):
        yield self




class Mailer:

    def __init__(self, host='localhost', port=25, use_tls=False,
                    username=None, password=None):
        self.host = host
        self.port = port
        self.use_tls = use_tls
        self.username = username
        self.password = password

    def login(self, username, password):
        self.username = username
        self.password = password

    def send(self, messages):
        server = smtplib.SMTP(self.host, self.port)

        if self.username and self.password:
            if self.use_tls is True:
                server.ehlo()
                server.starttls()
                server.ehlo()

            server.login(self.username, self.password)

        for m in messages:
            self._send(server, m)

        server.quit()

    def _send(self, server, msg):
        to = msg.To or []
        to = isinstance(to, basestring) and [to] or list(to)

        cc = msg.Cc or []
        cc = isinstance(cc, basestring) and [cc] or list(cc)

        bcc = msg.Bcc or []
        bcc = isinstance(bcc, basestring) and [bcc] or list(bcc)

        rcpts = to + cc + bcc
        server.sendmail(msg.From, rcpts, str(msg))






class Manager(threading.Thread):
    '''
    Send email in the background

    if a message was succesfully sent, self.status[msg.message_id] contains
    a tuple (True/False, code, message)
    '''

    def __init__(self, mailer=None, callback=None):
        threading.Thread.__init__(self)
        self.mailer = mailer
        self.queue = Queue.Queue()
        self.send = self.queue.put
        self.callback = callback
        self.running = True
        self.results = {}

    def halt(self):
        self.running = False

    def run(self):
        while self.running:
            msg = self.queue.get(block=True)
            if msg is None:
                break

            for m in msg:
                try:
                    self.status[m.message_id] = (False, -1, 'Not sent')
                    self.mailer.send(m)
                    self.status[m.message_id] = (True, 0, 'Sent successfully')
                except Exception, e:
                    args = e.args
                    if len(args) < 2:
                        args = (-1, e.args[0])

                    self.status[m.message_id] = (False, args[0], args[1])

                if self.callback is not None:
                    try:
                        self.callback(m)
                    except:
                        pass

            self.queue.task_done()




